<script setup lang="ts">
import ContextMenu from '@imengyu/vue3-context-menu';
import { invoke } from '@tauri-apps/api/core';
import { useMagicKeys } from '@vueuse/core';
import { showErrorNotification } from 'my-vue-ui-core/notification';
import { computed, h, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import draggable from 'vuedraggable';

import {
  IconBoxMultiple,
  IconEdit,
  IconFile,
  IconFolderOpen,
  IconPin,
  IconPinnedFilled,
  IconRefreshDot,
  IconTrash,
  IconUnlink,
} from '@tabler/icons-vue';

import LocalApp, { LocalAppWithUI } from '@/features/home/models/local-app';

const props = defineProps<{
  loading: boolean;
  localAppsWithUI: LocalAppWithUI[];
}>();

const emit = defineEmits<{
  (e: 'listChange', localAppsWithUI: LocalAppWithUI[]): void;
  (e: 'edit', localApp: LocalApp): void;
}>();

const { t } = useI18n();
const { ctrl } = useMagicKeys();

// draggable requires a ref to work properly with v-model.
// computed properties does not fit its needs.
const localAppsWithUI = ref<LocalAppWithUI[]>([]);

const isMultiItemSelected = computed<boolean>(
  () =>
    (localAppsWithUI.value ?? []).reduce(
      (count, item) => (item.isSelected ? count + 1 : count),
      0,
    ) > 1,
);

// props.localAppsWithUI value is assigned by async function.
// That's why watch is needed.
watch(
  () => props.localAppsWithUI,
  (newApps) => (localAppsWithUI.value = newApps),
);

/**
 * Opens input path, file or directory.
 */
async function openPath(path: String): Promise<void> {
  const result: string = await invoke('open_path', { path: path });

  if (result) {
    showErrorNotification(result);
  }
}

/**
 * Changes app pin status.
 */
function pin(item: LocalAppWithUI): void {
  item.isPinned = !item.isPinned;
  emit('listChange', localAppsWithUI.value);
}

/**
 * Unselects all apps and selects the clicked one.
 */
function selectSingleItem(item: LocalAppWithUI): void {
  // Sets all apps unselected.
  localAppsWithUI.value?.forEach((app) => {
    app.isSelected = false;
  });

  // Selects the clicked app.
  item.isSelected = true;
}

/**
 * Selects an app.
 * If ctrl key is pressed, toggles selection.
 * If not, selects the clicked app and unselects all others.
 */
function select(item: LocalAppWithUI): void {
  if (ctrl?.value == true) {
    item.isSelected = !item.isSelected;
  } else {
    selectSingleItem(item);
  }
}

/**
 * Opens all selected paths from the given app property name.
 */
function openSelectedPath<K extends keyof LocalAppWithUI>(propertName: K): void {
  localAppsWithUI.value?.forEach((app) => {
    if (app.isSelected && typeof app[propertName] === 'string') {
      openPath(app[propertName]);
    }
  });
}

/**
 * Emits listChange to tell the parent component that the list has changed.
 */
function removeSelectedItems(): void {
  localAppsWithUI.value = localAppsWithUI.value?.filter((item) => !item.isSelected);
  emit('listChange', localAppsWithUI.value);
}

/**
 * Displays context menu on right click.
 */
function onContextMenu(e: MouseEvent, item: LocalAppWithUI): void {
  // If item is selected, keeps it this way.
  // If not, selects it just like left click on it.
  if (!item.isSelected) {
    selectSingleItem(item);
  }

  // Prevents the browser's default menu.
  e.preventDefault();

  const iconSize = 18;

  // Shows custom context menu.
  ContextMenu.showContextMenu({
    x: e.x,
    y: e.y,
    items: [
      {
        icon: h(IconBoxMultiple, { size: iconSize }),
        label: t('run'),
        onClick: () => openSelectedPath('fileFullName'),
      },
      {
        icon: h(IconFolderOpen, { size: iconSize }),
        label: t('openDirectory'),
        onClick: () => openSelectedPath('filePath'),
      },
      { divided: true },
      {
        icon: h(IconEdit, { size: iconSize }),
        label: t('edit'),
        disabled: isMultiItemSelected,
        onClick: () => emit('edit', item),
      },
      {
        icon: h(IconTrash, { size: iconSize }),
        label: t('remove'),
        onClick: removeSelectedItems,
      },
    ],
  });
}
</script>

<template>
  <template v-if="localAppsWithUI.length > 0">
    <draggable
      v-model="localAppsWithUI"
      class="app-list-container"
      item-key="fileFullName"
      ghost-class="ghost"
      :animation="200"
      :force-fallback="true"
      @change="() => emit('listChange', localAppsWithUI)"
    >
      <template #item="{ element }">
        <div
          class="app-item"
          :class="{
            'app-item-selected': element.isSelected,
            'app-item-path-broken': element.isPathBroken,
          }"
          @mouseover="element.isHovered = true"
          @mouseleave="element.isHovered = false"
          @click="select(element)"
          @dblclick="openPath(element.fileFullName)"
          @contextmenu="onContextMenu($event, element)"
        >
          <el-icon
            size="18"
            class="app-pin-icon"
            v-show="element.isHovered || element.isPinned"
            @click="pin(element)"
          >
            <IconPinnedFilled v-show="element.isPinned" />
            <IconPin v-show="!element.isPinned" />
          </el-icon>
          <img v-if="element.iconBase64" :src="`data:image/png;base64,${element.iconBase64}`" />
          <el-icon size="36" v-if="element.isPathBroken == true">
            <IconUnlink />
          </el-icon>
          <el-tooltip
            :content="element.fileFullName"
            placement="bottom"
            :show-after="1000"
            :hide-after="100"
          >
            <div class="app-title">{{ element.title }}</div>
          </el-tooltip>
          <div v-show="element.isPinned" class="app-item-pinned-bottom"></div>
        </div>
      </template>
    </draggable>
  </template>
  <div class="welcome-info-container" v-else-if="loading">
    <el-icon size="120" class="rotating-element">
      <IconRefreshDot />
    </el-icon>
  </div>
  <div class="welcome-info-container" v-else>
    <el-icon size="36" style="margin-right: 10px">
      <IconFile />
    </el-icon>
    <span class="welcome-info-text">{{ t('welcomeInfo') }}</span>
  </div>
</template>

<style scoped>
.app-list-container {
  flex-grow: 1;
  overflow-x: auto;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-content: start;
  gap: 6px;
  padding: 2px 6px 6px 6px;
}

.app-list-container::-webkit-scrollbar {
  width: 0px;
}

.app-list-container:hover::-webkit-scrollbar {
  width: 4px;
}

.app-list-container:hover::-webkit-scrollbar-track {
  background: transparent;
}

.app-list-container:hover::-webkit-scrollbar-thumb {
  background-color: rgba(100, 100, 100, 0.5);
  border-radius: 0px;
}

.app-item {
  background-color: #fff;
  width: 110px;
  height: 75px;
  border: 1px solid rgb(71, 172, 240);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 4px;
  position: relative;
  user-select: none;
}

.app-item-selected {
  background-color: rgb(198, 227, 245);
}

.app-item-path-broken {
  color: #626060;
  background-color: rgb(212, 214, 215);
  font-style: italic;
}

.app-item-pinned-bottom {
  background-color: rgb(71, 172, 240);
  height: 10px;
  width: 100%;
  position: absolute;
  bottom: 0px;
}

.app-pin-icon {
  position: absolute;
  top: 4px;
  left: 4px;
}

.app-title {
  font-size: 0.8rem;
  color: #474444;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 90%;
}

@keyframes rotate {
  from {
    transform: rotate(360deg);
  }
  to {
    transform: rotate(0deg);
  }
}

.rotating-element {
  animation: rotate 2s linear infinite;
}

.welcome-info-container {
  display: flex;
  justify-content: center;
  align-items: center;
  color: gray;
  flex-grow: 1;
  user-select: none;
}

.welcome-info-text {
  font-size: 2rem;
}

.ghost {
  opacity: 0.4;
}
</style>
